package commons.base.http;

import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.Consts;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.fluent.Async;
import org.apache.http.client.fluent.Content;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import commons.base.util.StringUtil;


/**
 * HTTP请求服务
 * 支持有序请求和无序请求
 * 所有请求都是异步的
 * @author yuan<cihang.yuan@happyelements.com>
 *
 */
public class HttpService {

	private ExecutorService executorService;
	private CloseableHttpClient httpClient;
	
	/**异步无序HTTP请求运行器*/
	private final Async unOrderHttpRequestExecutor = Async.newInstance();
	
	/**异步有序HTTP请求运行器*/
	private OrderHttpRequestExecutor orderHttpRequestExecutor;
	
	public HttpService() {
		this(Runtime.getRuntime().availableProcessors(), -1);
	}
	
	public HttpService(int unOrderThreadCount, int orderThreadCount) {
		executorService = Executors.newFixedThreadPool(unOrderThreadCount);
		httpClient = createHttpClient();
		
		unOrderHttpRequestExecutor.use(Executor.newInstance(httpClient));
		unOrderHttpRequestExecutor.use(executorService);
		
		if(orderThreadCount > 0){
			orderHttpRequestExecutor = new OrderHttpRequestExecutor(orderThreadCount);
		}else if(orderThreadCount == 0){
			orderHttpRequestExecutor = new OrderHttpRequestExecutor();
		}
	}
	
	private CloseableHttpClient createHttpClient(){
		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", createAllowAllSSLSocketFactory())
                .build();;
		
		PoolingHttpClientConnectionManager connmgr = new PoolingHttpClientConnectionManager(registry);
		connmgr.setDefaultMaxPerRoute(100);
		connmgr.setMaxTotal(200);
		
		HttpClientBuilder httpClientBuilder = HttpClients.custom();
		httpClientBuilder.setConnectionManager(connmgr);
		
		RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
		requestConfigBuilder.setConnectTimeout(30000);
		requestConfigBuilder.setSocketTimeout(30000);
		
		httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
		
		return httpClientBuilder.build();
	}
	
	// 忽略https证书验证
	private SSLConnectionSocketFactory createAllowAllSSLSocketFactory() {
		try {
			SSLContext ctx = SSLContext.getInstance("TLS");
			
			X509TrustManager tm = new X509TrustManager() {
				public java.security.cert.X509Certificate[] getAcceptedIssuers() {
					return null;
				}

				public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
				}

				public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
				}
			};
			
			ctx.init(null, new TrustManager[] { tm }, null);
			
			HostnameVerifier hostnameVerifier = new HostnameVerifier(){
				public boolean verify(String hostname, SSLSession session) {
					return true;
				}
			};
			
			SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(ctx, hostnameVerifier);
			
			return ssf;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}
	
	public void shutdown(){
		if(executorService != null){
			executorService.shutdown();
		}
		if(httpClient != null){
			try {
				httpClient.close();
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
		if(orderHttpRequestExecutor != null){
			orderHttpRequestExecutor.shutdown();
		}
	}
	
	/**
	 * 发送无序HTTP JSON请求
	 * @param url
	 * @param bodyJson
	 * @param callback
	 */
	public void doPostJson(String url, String bodyJson,
			FutureCallback<Content> callback) {
		Request request = Request.Post(url);
		request.addHeader("Connection", "close");
		if(bodyJson != null){
			request.bodyString(bodyJson, ContentType.APPLICATION_JSON);
		}
		unOrderHttpRequestExecutor
				.execute(request, callback);
	}

	/**
	 * 发送无序HTTP JSON请求
	 * @param url
	 * @param headers
	 * @param bodyJson
	 * @param callback
	 */
	public void doPostJson(String url,
						   Map<String, String> headers,
						   String bodyJson,
						   FutureCallback<Content> callback) {
		Request request = Request.Post(url);
		for (String key : headers.keySet()) {
			request.addHeader(key, headers.get(key));
		}
		request.addHeader("Connection", "close");
		if(bodyJson != null){
			request.bodyString(bodyJson, ContentType.APPLICATION_JSON);
		}
		unOrderHttpRequestExecutor
				.execute(request, callback);
	}

	/**
	 * 发送无序HTTP请求
	 * @param url
	 * @param params
	 */
	public void doPost(String url, Iterable<? extends NameValuePair> params){
		doPost(url, params, new DefaultFutureCallback());
	}
	
	/**
	 * 发送无序HTTP请求
	 * @param url
	 * @param params
	 * @param callback
	 */
	public void doPost(String url, Iterable<? extends NameValuePair> params,
			FutureCallback<Content> callback) {
		unOrderHttpRequestExecutor
				.execute(buildPostRequest(url, params), callback);
	}

	/**
	 * 发送无序HTTP请求，可以定制Headers.
	 * @param url
	 * @param headers
	 * @param params
	 * @param callback
	 */
	public void doPost(String url,
					   Map<String, String> headers,
					   Iterable<? extends NameValuePair> params,
					   FutureCallback<Content> callback) {
		Request req = buildPostRequest(url, headers, params);
		unOrderHttpRequestExecutor.execute(req, callback);
	}
	
	/**
	 * 有序发送HTTP请求, 相同ID的会按顺序发送
	 * @param id
	 * @param url
	 * @param params
	 * @param callback
	 */
	public void doPost(long id, String url,
			Iterable<? extends NameValuePair> params,
			FutureCallback<Content> callback) {
		if(orderHttpRequestExecutor != null){
			orderHttpRequestExecutor
					.execute(id,
							buildPostRequest(url, params), 
							callback);
		}
	}
	
	/**
	 * 有序发送HTTP请求, 相同ID的会按顺序发送
	 * @param id
	 * @param url
	 * @param params
	 */
	public void doPost(long id, String url, Iterable<? extends NameValuePair> params){
		doPost(id, url, params, new DefaultFutureCallback());
	}

	// 构造HTTP POST请求对象，支持定制Headers
	private Request buildPostRequest(String url,
									 Map<String, String> headers,
									 Iterable<? extends NameValuePair> params) {
		Request req = buildPostRequest(url, params);
		for (String key : headers.keySet()) {
			req.addHeader(key, headers.get(key));
		}
		return req;
	}

	//构造HTTP POST请求对象
	private Request buildPostRequest(String url, Iterable<? extends NameValuePair> params){
		Request request = Request.Post(url);
		request.addHeader("Connection", "close");
		request.addHeader("Content-type", "application/x-www-form-urlencoded;charset=utf8");
		if(params != null){
			request.bodyForm(params, Consts.UTF_8);
		}
		
		return request;		
	}

	//构造HTTP GET请求对象
	private Request buildGetRequest(String url, Iterable<? extends NameValuePair> params){
		StringBuilder sb = new StringBuilder();
		if(params != null){
			Iterator<? extends NameValuePair> it = params.iterator();
			while(it.hasNext()){
				NameValuePair nameValuePair = it.next();
				sb.append(nameValuePair.getName()).append("=").append(nameValuePair.getValue()).append("&");
			}
		}
		
		if(sb.length() > 0){
			sb = StringUtil.compareAndDeleteLastChar(sb, '&');
			if(url.indexOf('&') != -1){//已经有参数
				url += "&" + sb.toString();
			}else{
				url +=	"?" + sb.toString();
			}
		}
		Request request = Request.Get(url);
		request.addHeader("Connection", "close");
		
		return request;		
	}

	private Request buildGetRequest(String url,
									Map<String, String> headers,
									Iterable<? extends NameValuePair> params) {
		Request req = buildGetRequest(url, params);
		for (String key : headers.keySet()) {
			req.addHeader(key, headers.get(key));
		}
		return req;
	}
	
	/**
	 * 发送无序HTTP请求
	 * @param url
	 * @param params
	 */
	public void doGet(String url, Iterable<? extends NameValuePair> params){
		doGet(url, params, new DefaultFutureCallback());
	}
	
	/**
	 * 发送无序HTTP请求
	 * @param url
	 * @param params
	 * @param callback
	 */
	public void doGet(String url, Iterable<? extends NameValuePair> params,
			FutureCallback<Content> callback) {
		unOrderHttpRequestExecutor
		.execute(buildGetRequest(url, params), callback);
	}

	/**
	 * 发送无序HTTP请求
	 * @param url
	 * @param headers
	 * @param params
	 * @param callback
	 */
	public void doGet(String url,
					  Map<String, String> headers,
					  Iterable<? extends NameValuePair> params,
					  FutureCallback<Content> callback) {
		unOrderHttpRequestExecutor
				.execute(buildGetRequest(url, headers, params), callback);
	}
	
	/**
	 * 有序发送HTTP请求, 相同ID的会按顺序发送
	 * @param id
	 * @param url
	 * @param params
	 * @param callback
	 */
	public void doGet(long id, String url,
			Iterable<? extends NameValuePair> params,
			FutureCallback<Content> callback) {
		if(orderHttpRequestExecutor != null){
			orderHttpRequestExecutor
					.execute(
							id,
							buildGetRequest(url, params), 
							callback);
		}
	}
	
	/**
	 * 有序发送HTTP请求, 相同ID的会按顺序发送
	 * @param id
	 * @param url
	 * @param params
	 */
	public void doGet(long id, String url, Iterable<? extends NameValuePair> params){
		doGet(id, url, params, new DefaultFutureCallback());
	}
	
}

/**默认回调实现*/
final class DefaultFutureCallback implements FutureCallback<Content>{

	private static final Logger LOG = LogManager.getLogger(DefaultFutureCallback.class);
	
	public void cancelled() {
		LOG.info("FutureCallback : cancelled");
	}

	public void completed(Content content) {
		String text = "";
		if(content != null){
			text = content.asString();
		}
		LOG.info("FutureCallback completed : {}", text);
	}

	public void failed(Exception e) {
		LOG.warn(e.getMessage(), e);
	}
	
}
